/*
 * Copyright (c) 2018 feeyo All Rights Reserved.
 * ProjectName: feeyoframework
 * FileName: AudioRecorder.kt
 * Auther: erik
 * Date: 12/26/18 4:52 PM
 * Version: 1.0
 * LastModified: 12/26/18 3:05 PM
 */

package com.feeyo.groundservice.easyphotoplus.audio

import android.content.Context
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subscribers.DisposableSubscriber
import java.io.IOException
import java.lang.IllegalStateException
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.log10

class AudioRecorder private constructor() {

    private val RECORDER_STATE_FAILURE = -1
    private val RECORDER_STATE_IDLE = 0
    private val RECORDER_STATE_STARTING = 1
    private val RECORDER_STATE_STOPPING = 2
    private val RECORDER_STATE_BUSY = 3

    // 音频源：音频输入-麦克风
    private val AUDIO_INPUT = MediaRecorder.AudioSource.MIC

    // 采样率
    // 44100是目前的标准，但是某些设备仍然支持22050，16000，11025
    // 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
    private val AUDIO_SAMPLE_RATE = 16000

    // 音频通道 单声道
    private val AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO

    // 音频格式：PCM编码
    private val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT

    // 缓冲区大小：缓冲区字节大小
    private var bufferSizeInBytes = 0

    private var recordBuffer: ByteArray? = null

    private var recorderState = RECORDER_STATE_IDLE

    private var mRecorderSampleRate = 8000

    private var audioSaveHelper: AudioSaveHelper = AudioSaveHelper()

    private val mIsPaused = AtomicBoolean(false)

    private var recordListener: RecordListener? = null

    private var started = AtomicBoolean(false)

    private var disposable: Disposable? = null

    private var mediaRecorder: MediaRecorder? = null

    private var startTm: Long = 0       // 录音开始时间


    private var micDbListener: MicDbListener? = null
    private var timeDisposable: Disposable? = null

    companion object {
        private const val MIC_DB_INTERNAL = 300L   // 获取分贝的时间间隔（毫秒）

        private var instance: AudioRecorder? = null

        @JvmStatic
        fun getInstance(): AudioRecorder {
            if (null == instance) {
                synchronized(AudioRecorder::class) {
                    if (instance == null) {
                        instance = AudioRecorder()
                    }
                }
            }
            return instance!!
        }
    }

    private fun getAudioDataFlowable(context: Context? = null): Flowable<ByteArray> {
        return Flowable.create<ByteArray>({ emitter ->
            val bufferSize = 4 * 1024
            val recorder = AudioRecord(
                MediaRecorder.AudioSource.MIC, mRecorderSampleRate,
                Constants.RECORDER_CHANNELS, Constants.RECORDER_AUDIO_ENCODING, bufferSize
            )
            audioSaveHelper.createNewFile(context)

            recordListener?.let {
                it.onFilePath(audioSaveHelper.filePath)
            }

            try {
                if (recorderState == RECORDER_STATE_STARTING) {
                    recorderState = RECORDER_STATE_BUSY
                }
                recorder.startRecording()

                recordBuffer = ByteArray(bufferSize)
                do {
                    if (!mIsPaused.get()) {
                        val bytesRead = recorder.read(recordBuffer!!, 0, bufferSize)
                        emitter.onNext(recordBuffer!!)
                        if (bytesRead == 0) {
                            Log.e(AudioRecorder::class.java.simpleName, "error: $bytesRead")
                            onRecordFailure()
                        }
                    }
                } while (started.get())
            } finally {
                recorder.release()
            }
            emitter.onComplete()
        }, BackpressureStrategy.DROP)
    }

    /**
     * 开始录音 <br>
     * 录音前请确认RECORD_AUDIO和WRITE_EXTERNAL_STORAGE权限被允许
     * @param context 用于获取app文件目录
     * @param quality Boolean 录音质量 true为高质量
     * @param listener 录音回调，返回录音文件路径
     */
    fun startRecord(context: Context? = null, quality: Boolean = false, listener: RecordListener? = null, micDbListener: MicDbListener? = null) {
        this.recordListener = listener
        this.micDbListener = micDbListener

        if (recorderState != RECORDER_STATE_IDLE) {
            return
        }

        startTm = System.currentTimeMillis()

        if (quality) {
            mRecorderSampleRate =
                if (quality) Constants.RECORDER_SAMPLE_RATE_HIGH else Constants.RECORDER_SAMPLE_RATE_LOW
            audioSaveHelper.mRecordSampleRate = mRecorderSampleRate

            recorderState = RECORDER_STATE_STARTING

            startRecordThread(context)
        } else {
            started.set(true)
            if (null == mediaRecorder)
                mediaRecorder = MediaRecorder()

            mediaRecorder?.let {
                try {
                    it.setAudioSource(MediaRecorder.AudioSource.MIC)
                    it.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS)
                    it.setAudioSamplingRate(AUDIO_SAMPLE_RATE)
                    it.setAudioChannels(1)

                    val mFilePath = audioSaveHelper.getAudioPath(context, Constants.AUDIO_RECORDER_FILE_EXT_AAC)

                    it.setOutputFile(mFilePath)

                    listener?.onFilePath(mFilePath)

                    // 编码格式
                    it.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)

                    try {
                        it.prepare()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }

                    it.start()
                } catch (e: IllegalStateException) {
                    e.printStackTrace()
                }

                micDbListener?.let {
                    startGetDb()
                }
            }
        }
    }

    /**
     * 停止录音
     */
    fun stopRecord() {
        if (isRecording())
            finishRecord()

        if (System.currentTimeMillis() - startTm < 1000L) { // 避免立即stop引起的RuntimeException
            Flowable.timer(1, TimeUnit.SECONDS)
                .subscribe {
                    nativeStop()
                }
        } else {
            nativeStop()
        }

        recordListener = null
    }

    private fun nativeStop() {
        try {
            mediaRecorder?.stop()
        } catch (e: IllegalStateException) {
            e.printStackTrace()
        }
    }

    private fun startRecordThread(context: Context? = null) {
        started.set(true)

        disposable = getAudioDataFlowable(context).subscribeOn(Schedulers.io()).subscribeWith(object : DisposableSubscriber<ByteArray>() {
            override fun onComplete() {
                audioSaveHelper.onRecordingStopped()
                recorderState = RECORDER_STATE_IDLE
            }

            override fun onNext(t: ByteArray?) {
                audioSaveHelper.onDataReady(recordBuffer!!)
            }

            override fun onError(t: Throwable?) {
                t?.let { println(it.message) }
            }
        })
    }

    private fun finishRecord() {
        started.set(false)
        stopGetDb()
    }

    fun isRecording(): Boolean {
        return started.get()
    }

    private fun onRecordFailure() {
        recorderState = RECORDER_STATE_FAILURE
        finishRecord()
    }


    private fun startGetDb() {
        timeDisposable?.let {
            if (!it.isDisposed)
                it.dispose()
        }

        timeDisposable = Flowable.interval(MIC_DB_INTERNAL, TimeUnit.MILLISECONDS)
            .subscribe {
                micDbListener?.onDbChange(getAudioDb())
            }
    }

    private fun stopGetDb() {
        timeDisposable?.let {
            if (!it.isDisposed)
                it.dispose()
        }

        micDbListener = null
    }

    /**
     * 获取mic 分贝
     */
    private fun getAudioDb(): Int {
        var db = 0
        mediaRecorder?.let {
            val BASE = 600
            val ratio = (it.maxAmplitude / BASE).toDouble()
            if (ratio > 1)
                db = (20 * log10(ratio)).toInt()
        }
        return db
    }

    /**
     * 获取录音文件录音listener
     */
    interface RecordListener {
        fun onFilePath(path: String?)
    }

    /**
     * 获取分贝listener
     */
    interface MicDbListener {
        fun onDbChange(db: Int)
    }
}